#!/usr/bin/env python
# -*- coding: utf-8 -*-

__author__ = 'Michael Liao'

import os
import time
import uuid
import json
import random
import urllib
import urllib2
import hashlib
import logging

from framework import cache, next_id, render, handler, emptyobject, create_oauth_cookie, COOKIE_NAME, COOKIE_EXPIRES
from appkey import _CLIENT_ID, _CLIENT_SECRET, _REDIRECT_URI

import web

from helper import *

################################################################################
# url functions                                                                #
################################################################################

@handler('GET')
def welcome():
    '''
    GET /

    Show welcome page.
    '''
    d = dict(user=web.ctx.user)
    return render('welcome.html', d)

@handler('GET')
def index():
    '''
    GET /{domain}
    
    Show index page of domain.
    '''
    return dict(roots=load_roots())

def _render_node(domain, node, selected_id, level):
    css, white = ('active', 'white') if node.id==selected_id else ('None', 'None')
    if level==0:
        icon = 'lock' if node.private else 'home'
        s = u'<li class="%s level-0"><a href="/%s/doc/%s/"><i class="icon-%s icon-%s"></i>%s</a></li>' % (css, domain, node.id, white, icon, node.title)
    else:
        s = u'<li class="%s level-%d"><a href="/%s/doc/%s/%s"><i class="icon-%s icon-file"></i>%s</a></li>' % (css, level, domain, node.root_id, node.id, white, node.title)
    L = [_render_node(domain, c, selected_id, level+1) for c in node.children]
    return u'%s\n%s' % (s, u''.join(L))

@handler('GET')
def doc(root_id, node_id=''):
    '''
    GET /doc/{root_id}/{node_id}

    Show doc and its related tree.
    '''
    roots, root = load_roots(root_id)
    if node_id:
        node = load_node(node_id, root_id)
    else:
        node = root
    load_tree(root)
    return dict(roots=roots, root=root, node=node, func_render_node=_render_node)

@handler('GET')
def add(root_id, parent_id):
    '''
    GET /add/{parent_id}

    show add new page.
    '''
    roots, root = load_roots(root_id)
    if root_id==parent_id:
        node = root
    else:
        node = load_node(parent_id, root_id)
    load_tree(root)
    return dict(add=True, root=root, node=node, root_id=root_id, parent_id=parent_id, action='_add', editing=emptyobject(), func_render_node=_render_node, __include__='edit.html')

@handler('POST')
def _add():
    '''
    POST _add
    '''
    i = web.input()
    roots, root = load_roots(i.root_id)
    parent_is_root = i.root_id==i.parent_id
    parent = root if parent_is_root else load_node(i.parent_id, i.root_id)
    # check:
    id = next_id()
    current = current_time()
    kw = dict(id = id,
            root_id = root.id,
            parent_id = parent.id,
            title = i.title,
            content = i.content,
            display_order = current,
            creation_time = current,
            modified_time = current,
            version = 0)
    web.ctx.db.insert('kbnode', **kw)
    raise web.seeother('/%s/doc/%s/%s' % (web.ctx.customer.domain, root.id, id))

@handler('GET')
def edit(root_id, node_id):
    '''
    GET /edit/{root_id}/{node_id}
    
    Show edit page.
    '''
    roots, root = load_roots(root_id)
    if root_id==node_id:
        node = root
    else:
        node = load_node(node_id, root_id)
    load_tree(root)
    return dict(add=False, root=root, node=node, root_id=root_id, node_id=node_id, action='_edit', editing=node, func_render_node=_render_node, __include__='edit.html')

@handler('POST')
def _edit():
    '''
    POST /_edit
    '''
    i = web.input()
    root = load_root(i.root_id)
    editing_root = i.root_id==i.id
    node = root if editing_root else load_node(i.id, i.root_id)
    # check:
    kw = dict(
            title = i.title,
            content = i.content,
            modified_time = current_time(),
            version = web.db.sqlliteral('version + 1'))
    table = 'kbroot' if editing_root else 'kbnode'
    web.ctx.db.update(table, where='id=$id', vars=dict(id=i.id), **kw)
    if editing_root:
        raise web.seeother('/%s/doc/%s/' % (web.ctx.customer.domain, i.root_id))
    raise web.seeother('/%s/doc/%s/%s' % (web.ctx.customer.domain, i.root_id, i.id))

@handler('POST')
def _delete():
    '''
    POST /_delete

    id=12345
    '''
    i = web.input()
    id = i.id
    root_id = i.root_id
    is_root = id==root_id
    root = load_root(root_id)
    check_write(root)
    node = root if is_root else load_node(id, root_id)
    # check if it is has child:
    children = list(web.ctx.db.query('select id from kbnode where parent_id=$id limit 1', vars=dict(id=id)))
    if children:
        raise web.notfound('cannot delete non-empty node')
    table = 'kbroot' if is_root else 'kbnode'
    web.ctx.db.delete(table, where='id=$id', vars=dict(id=id))
    if is_root:
        web.ctx.db.delete('kbnode', where='root_id=$id', vars=dict(id=root_id))
        raise web.seeother('/%s/' % web.ctx.customer.domain)
    # it is a root node:
    raise web.seeother('/%s/%s/' % (web.ctx.customer.domain, root_id))

@handler('POST', 'application/json')
def _upload(filetype):
    _check_permission('w', 'upload')
    if filetype=='image':
        # upload image:
        i = web.input(file={})
        id = next_id()
        if 'file' in i:
            ext = os.path.splitext(i.file.filename.lower())[1]
            if ext not in ('.png', '.gif', '.jpg', '.jpeg'):
                raise web.badrequest()
            fname = '%s%s' % (id, ext)
            savedir = os.path.join(os.path.split(__file__)[0], 'static', 'upload', id[:2], id[-2:])
            try:
                if not os.path.isdir(savedir):
                    os.makedirs(savedir)
                fout = open('%s/%s' % (savedir, fname), 'w')
                fout.write(i.file.file.read())
                fout.close()
            except Exception:
                raise web.internalerror('io error')
            return r'{"status": "0", "imgUrl":"/static/upload/%s/%s/%s"}' % (id[:2], id[-2:], fname)
    raise web.badrequest()

@handler('GET')
def auth_signout():
    '''
    Sign out and redirect to previous page.
    '''
    web.setcookie(COOKIE_NAME, 'deleted', -360000)
    web.seeother(get_referer())

@handler('GET')
def auth_signin():
    '''
    Redirect to Google oauth2 sign in page.
    '''
    url = encode_url('https://accounts.google.com/o/oauth2/auth', \
            response_type = 'code', \
            client_id = _CLIENT_ID, \
            redirect_uri = _REDIRECT_URI, \
            scope = 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email', \
            access_type = 'online', \
            state = 'auth')
    logging.info('auth_signin: %s' % url)
    raise web.seeother(url)

@handler('GET')
def auth_customer():
    '''
    Redirect to customer's home page.
    '''
    customer_id = web.input(id=u'').id
    if customer_id:
        cs = list(web.ctx.db.query('select * from kbcustomer where id=$id', vars=dict(id=customer_id)))
        if cs:
            raise web.redirect('/%s' % cs[0].domain)
    raise web.notfound('404 bad customer id')

@handler('GET')
def auth_callback():
    '''
    Callback from Google OAuth2 server.
    '''
    i = web.input(code=u'', error=u'')
    if i.error or not i.code:
        return dict(error=u'Authentication failed.')
    code = i.code
    logging.info('auth_callback: got code: %s' % code)
    # verify the code:
    params = dict(code = code, \
        client_id = _CLIENT_ID, \
        client_secret = _CLIENT_SECRET, \
        redirect_uri = _REDIRECT_URI, \
        grant_type = 'authorization_code')
    r = http_call('https://accounts.google.com/o/oauth2/token', encode_params(**params))
    logging.info('auth_callback: verify code result: %s' % r)
    access_token = r.access_token
    expires_in = int(time.time()) + r.expires_in
    url = encode_url('https://www.googleapis.com/oauth2/v1/userinfo', access_token=access_token)
    info = http_call(url)
    logging.info('auth_callback: got user info: %s' % info)
    if not info.verified_email:
        return dict(error=u'unverified email')
    # check user:
    email = info.email.lower()
    domain = email.split('@')[1]
    customer = find_customer(domain)
    if customer is None:
        return dict(error=u'Domain not registered: %s' % domain)
    admin = 1 if customer.email==email else 0
    picture = info.picture
    if not picture:
        picture = '/static/img/user_%s.png' % info.gender
    user_dict = dict( \
            name = info.name, \
            picture = picture, \
            locale = info.locale, \
            timezone = info.timezone, \
            gender = info.gender, \
            access_token = access_token, \
            expires_in = expires_in)
    current = current_time()
    users = list(web.ctx.db.query('select * from kbuser where email=$email', vars=dict(email=email)))
    if users:
        user = users[0]
        id = user.id
        logging.info('update user: %s' % user.email)
        user_dict['modified_time'] = current
        user_dict['version'] = web.db.sqlliteral('version + 1')
        web.ctx.db.update('kbuser', where='id=$id', vars=dict(id=user.id), **user_dict)
    else:
        if admin==0:
            check_plan(customer)
        logging.info('create new user: %s' % email)
        id = next_id()
        user_dict['id'] = id
        user_dict['customer_id'] = customer.id
        user_dict['admin'] = admin
        user_dict['email'] = email
        user_dict['creation_time'] = current
        user_dict['modified_time'] = current
        user_dict['version'] = 0
        web.ctx.db.insert('kbuser', **user_dict)
        web.ctx.db.update('kbcustomer', where='id=$id', vars=dict(id=customer.id), user_count=web.sqlliteral('user_count+1'))
    logging.info('make signin cookie...')
    web.setcookie(COOKIE_NAME, create_oauth_cookie(id, expires_in, email), COOKIE_EXPIRES)
    raise web.seeother('/%s' % domain)

_PLANS = { '0':20, '1':100, '2':500, '3':5000 }

def check_plan(customer):
    max = _PLANS[str(customer.plan)]
    if customer.user_count >= max:
        raise web.badrequest('max users reached.')

def _obj_hook(pairs):
    '''
    convert json object to python object.
    '''
    o = JsonObject()
    for k, v in pairs.iteritems():
        o[str(k)] = v
    return o

class JsonObject(dict):
    '''
    general json object that can bind any fields but also act as a dict.
    '''
    def __getattr__(self, attr):
        if attr in self:
            return self[attr]
        return u''

    def __setattr__(self, attr, value):
        self[attr] = value













def _check_permission(rw, resource):
    user = web.ctx.user
    if user is None:
        raise web.seeother('/auth_signin')
    if isinstance(resource, KbNode):
        logging.info('check KbNode...')
        if rw=='w' and resource.user_id!=user.id:
            raise web.badrequest()
    return True
